package org.zyx.service;


import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.LongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.zyx.client.BrandClient;
import org.zyx.client.CategoryClient;
import org.zyx.client.GoodsClient;
import org.zyx.client.SpecificationClient;
import org.zyx.entity.Goods;
import org.zyx.entity.SearchParams;
import org.zyx.entity.SearchResult;
import org.zyx.item.entity.*;
import org.zyx.repository.GoodsRepository;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @Author: SwordMaple
 * @Date: 2020/9/20  20:36
 * @Description: 搜索服务处理
 * @Version: 1.0
 */
@Service
public class SearchService {

    @Resource
    private CategoryClient categoryClient;
    @Resource
    private BrandClient brandClient;
    @Resource
    private SpecificationClient specificationClient;
    @Resource
    private GoodsClient goodsClient;
    @Resource
    private GoodsRepository goodsRepository;


    private static final ObjectMapper MAPPER = new ObjectMapper();

    /**
     * 通过spu转换为goods
     *
     * @param spu
     * @return
     */
    public Goods buildGoods(Spu spu) throws IOException {
        Goods goods = new Goods();

        //根据分类id查询分类名称
        List<String> names = categoryClient.queryNameByIds(Arrays.asList(spu.getCid1(), spu.getCid2(), spu.getCid3()));
        //根据品牌id查询品牌
        Brand brand = brandClient.findBrandById(spu.getBrandId());
        //根据spuId查询所有sku
        List<Sku> skus = goodsClient.findSkusBySpuId(spu.getId());
        //初始化价格集合,收集所有sku的价格
        ArrayList<Long> prices = new ArrayList<Long>();
        //手机skus的必要信息字段
        List<Map<String, Object>> skuMapList = new ArrayList<>();

        skus.forEach(sku -> {
            prices.add(sku.getPrice());

            //将每一个Sku的必要信息放入map集合中,方便转化成json字段
            Map<String, Object> skuMap = new HashMap();
            skuMap.put("id", sku.getId());
            skuMap.put("title", sku.getTitle());
            skuMap.put("price", sku.getPrice());
            //获取sku中的图片,数据库中的图片可能有多张,以","分隔,我们只需要第一张图片
            skuMap.put("image", StringUtils.isBlank(sku.getImages()) ? "" : StringUtils.split(sku.getImages(), ",")[0]);
            skuMapList.add(skuMap);
        });

        //通过spu中的cid3查询出所有的搜索规格参数
        List<SpecParam> params = specificationClient.findParams(null, spu.getCid3(), null, true);

        //1根据spuid查询spu_detail
        SpuDetail detail = goodsClient.findSpuDetailById(spu.getId());
        //2.把通用的规格参数进行反序列化
        Map<String, Object> genericSpecMap = MAPPER.readValue(detail.getGenericSpec(), new TypeReference<Map<String, Object>>() {
        });
        //3.把特殊的规格参数值进行序列化
        Map<String, List<Object>> specialSpecMap = MAPPER.readValue(detail.getSpecialSpec(), new TypeReference<Map<String, List<Object>>>() {
        });
        //4.
        Map<String, Object> spec = new HashMap();
        params.forEach(param -> {
            //判断规格参数的类型是否是通用的规格参数
            if (param.getGeneric()) {
                //如果是通用类型的参数从genericSpecMap中获取参数
                String value = genericSpecMap.get(param.getId().toString()).toString();
                if (param.getNumeric()) {  //判断是否是数值类型的参数,如果是,应该返回一个区间
                    value = chooseSegment(value, param);
                }
                spec.put(param.getName(), value);
            } else {
                //如果是特殊规格参数,则在specialSpecMap中获取参数
                spec.put(param.getName(), specialSpecMap.get(param.getId().toString()));
            }

        });


        goods.setId(spu.getId());
        //商品三级分类
        goods.setCid1(spu.getCid1());
        goods.setCid2(spu.getCid2());
        goods.setCid3(spu.getCid3());
        //品牌id
        goods.setBrandId(spu.getBrandId());
        goods.setCreateTime(spu.getCreateTime());
        //spu子标题
        goods.setSubTitle(spu.getSubTitle());
        //拼接all字段,需要分类名称以及品牌名称
        goods.setAll(spu.getTitle() + " " + StringUtils.join(names, " ") + " " + brand.getName());
        //获取spu下的所有sku价格
        goods.setPrice(prices);
        //获取spu下的所有sku并转化成json字符串保存
        goods.setSkus(MAPPER.writeValueAsString(skuMapList));
        //获取所有查询的规格参数{name:value}

        goods.setSpecs(spec);

        return goods;
    }


    public String chooseSegment(String value, SpecParam param) {

        double val = NumberUtils.toDouble(value);
        String result = "其它";
        //保存数值段
        for (String segment : param.getSegments().split(",")) {
            String[] segs = segment.split("-");
            //获取数值范围
            double begin = NumberUtils.toDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if (segs.length == 2) {
                end = NumberUtils.toDouble(segs[1]);
            }
            //判断是否范围内
            if (val >= begin && val < end) {
                if (segs.length == 1) {
                    result = segs[0] + param.getUnit() + "以上";
                } else if (begin == 0) {
                    result = segs[1] + param.getUnit() + "以下";
                } else {
                    result = segment + param.getUnit();
                }
                break;
            }

        }
        return result;
    }

    /**
     * 根据参数分页查询商品
     *
     * @param request
     * @return
     */
    public SearchResult search(SearchParams request) {
        String key = request.getKey();
        // 判断是否有搜索条件，如果没有，直接返回null。不允许搜索全部商品
        if (StringUtils.isBlank(key)) {
            return null;
        }

        // 构建查询条件
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();

        // 对key进行全文检索查询
        //QueryBuilder basicQuery = QueryBuilders.matchQuery("all", key).operator(Operator.AND);

        //添加查询条件
        BoolQueryBuilder basicQuery = buildBoolQueryBuilder(request);
        queryBuilder.withQuery(basicQuery);

        // 通过sourceFilter设置返回的结果字段,我们只需要id、skus、subTitle
        queryBuilder.withSourceFilter(new FetchSourceFilter(
                new String[]{"id", "skus", "subTitle"}, null));

        // 分页
        // 准备分页参数
        int page = request.getPage();
        int size = request.getSize();
        queryBuilder.withPageable(PageRequest.of(page - 1, size));

        //添加分类和品牌的聚合
        String categoryAggName = "category";
        String brandAggName = "brand";
        //对分类进行聚合
        queryBuilder.addAggregation(AggregationBuilders.terms(categoryAggName).field("cid3"));
        //对品牌进行聚合
        queryBuilder.addAggregation(AggregationBuilders.terms(brandAggName).field("brandId"));
        //查询，获取结果
        AggregatedPage<Goods> pageInfo = (AggregatedPage<Goods>) this.goodsRepository.search(queryBuilder.build());
        //获取聚合结果集,并解析
        List<Map<String, Object>> categories = getCategoryAggResult(pageInfo.getAggregation(categoryAggName));
        List<Brand> brands = getBrandAggResult(pageInfo.getAggregation(brandAggName));
        List<Map<String, Object>> specs = null;
        //判断是否只有一个分类,只有一个分类时,才做规格参数的聚合
        if(!CollectionUtils.isEmpty(categories) && categories.size() == 1){
            //对规格参数进行聚合
            specs = getParamAggResult((Long)categories.get(0).get("id"),basicQuery);
        }

        //计算总页数
        Integer totalPages = ((int) Math.ceil(pageInfo.getTotalElements() / (double) size));
        // 封装结果并返回
        return new SearchResult(pageInfo.getTotalElements(), totalPages, pageInfo.getContent(),
                categories, brands,specs);

    }

    /**
     * 构建布尔查询
     * @param request
     * @return
     */
    private BoolQueryBuilder buildBoolQueryBuilder(SearchParams request) {

        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        //添加基本查询条件
        boolQueryBuilder.must(QueryBuilders.matchQuery("all", request.getKey()).operator(Operator.AND));//必要条件
        //添加过滤查询条件
        //1.获取用户选择的过滤信息
        Map<String, Object> filters = request.getFilter();
        //遍历过滤信息
        for (Map.Entry<String, Object> entry : filters.entrySet()) { //遍历map集合
            String key = entry.getKey();
            if(StringUtils.equals("brandId",key)){
                key = "brandId";
            }else if(StringUtils.equals("cid3",key)){
                key = "cid3";
            }else{
                key = "specs."+key+".keyword";
            }
            boolQueryBuilder.filter(QueryBuilders.termQuery(key,entry.getValue()));//添加过滤条件,根据词条查询
        }

        return boolQueryBuilder;
    }

    /**
     * 根据查询条件聚合规格参数
     * @param cid 分类id
     * @param basicQuery 基础查询条件
     * @return
     */
    private List<Map<String, Object>> getParamAggResult(Long cid, QueryBuilder basicQuery) {

        List<Map<String, Object>> specs = new ArrayList<>();

        //自定义查询对象构建器
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        //添加基本查询条件
        queryBuilder.withQuery(basicQuery);
        //查询要聚合的规格参数
        List<SpecParam> params = specificationClient.findParams(null, cid, null, true);
        //添加规格参数的聚合
        params.forEach(param -> {
            queryBuilder.addAggregation(AggregationBuilders.terms(param.getName()).field("specs."+param.getName()+".keyword"));
        });
        //添加结果集过滤
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{},null));
        //执行聚合查询,获取聚合结果集
        AggregatedPage<Goods> goodsPage = (AggregatedPage<Goods>)goodsRepository.search(queryBuilder.build());
        //解析聚合结果集, key:聚合名称(规格参数名)  value:聚合对象
        Map<String, Aggregation> aggregationMap = goodsPage.getAggregations().asMap();

        for (Map.Entry<String, Aggregation> entry : aggregationMap.entrySet()) {
            //初始化一个map,{key:规格参数名,options:聚合的规格参数值}
            Map<String,Object> map = new HashMap<>();
            map.put("k",entry.getKey());
            //初始化一个options集合,搜集桶中的key
            List<String> options = new ArrayList<>();
            //获取聚合
            StringTerms terms = (StringTerms) entry.getValue();
            //获取桶集合
            terms.getBuckets().forEach(bucket -> {
                options.add(bucket.getKeyAsString());
            });
            map.put("options",options);
            //收集参数集
            specs.add(map);
        }
        return specs;
    }

    /**
     * 解析品牌的聚合结果集
     *
     * @param aggregation
     * @return
     */
    private List<Brand> getBrandAggResult(Aggregation aggregation) {
        //key为Long,使用LongTerms
        LongTerms terms = (LongTerms) aggregation;
        //获取集合中的桶
        return terms.getBuckets().stream().map(bucket -> {
                    return brandClient.findBrandById(bucket.getKeyAsNumber().longValue());
                }
        ).collect(Collectors.toList());
    }

    /**
     * 解析分类聚合结果集
     *
     * @param aggregation
     * @return
     */
    private List<Map<String, Object>> getCategoryAggResult(Aggregation aggregation) {
        //key为Long,使用LongTerms
        LongTerms terms = (LongTerms) aggregation;
        //获取桶的集合,转换成List<Map<String, Object>>集合
        return terms.getBuckets().stream().map(bucket -> {
                    //初始化一个Map
                    Map<String, Object> map = new HashMap<>();
                    //获取桶中分类的id(key)
                    Long id = bucket.getKeyAsNumber().longValue();
                    //根据分类ID查询分类名称
                    List<String> names = categoryClient.queryNameByIds(Arrays.asList(id));
                    map.put("id", id);
                    map.put("name", names.get(0));
                    return map;
                }
        ).collect(Collectors.toList());
    }

    /**
     * 保存新增的商品
     * @param spuId
     */
    public void save(Long spuId) throws IOException {

        Spu spu = this.goodsClient.findSpuById(spuId);
        Goods goods = this.buildGoods(spu);
        this.goodsRepository.save(goods);



    }

    /**
     * 删除库中数据
     * @param spuId
     */
    public void delete(Long spuId) {
        this.goodsRepository.deleteById(spuId);
    }
}
